package org.boticelli;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import org.boticelli.plugin.BoticelliPlugin;
import org.boticelli.plugin.HelpfulBoticelliPlugin;
import org.boticelli.plugin.PluginResult;
import org.boticelli.util.Util;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import f00f.net.irc.martyr.GenericAutoService;
import f00f.net.irc.martyr.InCommand;
import f00f.net.irc.martyr.State;
import f00f.net.irc.martyr.commands.MessageCommand;

/**
 * Dispatches all incoming messages to the plugins and handles help.
 * 
 * @author shelmberger
 */
public class MessageDispatcher
        extends GenericAutoService
{
    private static org.slf4j.Logger log = LoggerFactory.getLogger(MessageDispatcher.class);

    private Bot bot;

    private List<BoticelliPlugin> plugins;

    private List<HelpfulBoticelliPlugin> helpfulPlugins;

    private final static String HELP_COMMAND = "help";

    private final static String HELP_PREFIX = HELP_COMMAND + " ";

    public MessageDispatcher(Bot bot, List<BoticelliPlugin> plugins)
    {
        super(bot.getConnection());
        this.bot = bot;
        this.plugins = plugins;

        helpfulPlugins = new ArrayList<HelpfulBoticelliPlugin>();
        for (BoticelliPlugin plugin : plugins)
        {
            if (plugin instanceof HelpfulBoticelliPlugin)
            {
                helpfulPlugins.add((HelpfulBoticelliPlugin) plugin);
            }
        }

        log.debug("helpful: {}", helpfulPlugins);

        enable();
    }

    @Override
    public void updateCommand(InCommand command)
    {
        SessionFactory sessionFactory = bot.getSessionFactory();
        boolean participate = false;
        try
        {
            if (TransactionSynchronizationManager.hasResource(sessionFactory))
            {
                // Do not modify the Session: just set the participate flag.
                participate = true;
            }
            else
            {
                log.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
                Session session = getSession(sessionFactory);
                TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(
                        session));
            }

            updateCommandInternal(command);
        }
        catch(Exception e)
        {
            log.error("Error during updateCommand: ", e);
        }
        finally
        {
            if (!participate)
            {
                SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
                        .unbindResource(sessionFactory);
                log.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
                closeSession(sessionHolder.getSession(), sessionFactory);
            }
        }
    }

    protected void updateCommandInternal(InCommand command)
    {
        log.debug("receving command = {}", command);
        
        PluginResult result = PluginResult.NEXT;
        for (BoticelliPlugin plugin : plugins)
        {
            if (plugin.supports(command.getClass()))
            {
                if (result != PluginResult.SKIP_NEXT)
                {
                    try
                    {
                        result = plugin.handle(bot, command);
                        if (result == PluginResult.STOP)
                        {
                            break;
                        }
                    }
                    catch (Exception e)
                    {
                        log.warn("error executing plugin " + plugin, e);
                    }
                }
                else
                {
                    result = PluginResult.NEXT;
                }
            }
        }

        if (command instanceof MessageCommand)
        {
            handleHelp((MessageCommand) command);
        }
    }

    private void handleHelp(MessageCommand msg)
    {
        String inMessage = msg.getMessage();
        boolean help = inMessage.equals(HELP_COMMAND);
        if (msg.isPrivateToUs(bot.getState()) && inMessage.startsWith(HELP_PREFIX) || help)
        {

            String out = null;
            if (help)
            {
                out = generalHelp();
            }
            else
            {
                List<String> args = Util.split(msg.getMessage().substring(HELP_COMMAND.length()),
                        " ");
                String helpName = args.remove(0);
                for (HelpfulBoticelliPlugin helpful : helpfulPlugins)
                {
                    if (helpful.getHelpName().equals(helpName))
                    {
                        out = helpful.helpText(bot, args);
                        break;
                    }
                }
                if (out == null)
                {
                    out = "Help for " + helpName + " not found.";
                }
            }

            StringTokenizer tokenizer=new StringTokenizer(out, "\n");
            while (tokenizer.hasMoreTokens())
            {
                out=tokenizer.nextToken();
                bot.sendMessage(msg.getSource(), out);
            }
        }
    }

    private String generalHelp()
    {
        if (helpfulPlugins.size() == 0)
        {
            return "no help available";
        }

        StringBuilder sb = new StringBuilder();
        sb.append("Use \002/msg ").append(bot.getNick()).append(" help <name>\002 where name is ");
        boolean first = true;
        for (Iterator<HelpfulBoticelliPlugin> i = helpfulPlugins.iterator(); i.hasNext();)
        {
            HelpfulBoticelliPlugin helpful = i.next();
            if (!first)
            {
                boolean isLast = !i.hasNext();
                if (isLast)
                {
                    sb.append(" or ");
                }
                else
                {
                    sb.append(", ");
                }
            }
            sb.append("\002").append(helpful.getHelpName()).append("\002");
            first = false;
        }
        return sb.toString();
    }

    @Override
    protected void updateState(State state)
    {
    }

    /**
     * Get a Session for the SessionFactory that this filter uses. Note that
     * this just applies in single session mode!
     * <p>
     * The default implementation delegates to the
     * <code>SessionFactoryUtils.getSession</code> method and sets the
     * <code>Session</code>'s flush mode to "NEVER".
     * <p>
     * Can be overridden in subclasses for creating a Session with a custom
     * entity interceptor or JDBC exception translator.
     * 
     * @param sessionFactory
     *            the SessionFactory that this filter uses
     * @return the Session to use
     * @throws DataAccessResourceFailureException
     *             if the Session could not be created
     * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory,
     *      boolean)
     * @see org.hibernate.FlushMode#NEVER
     */
    protected Session getSession(SessionFactory sessionFactory)
            throws DataAccessResourceFailureException
    {
        Session session = SessionFactoryUtils.getSession(sessionFactory, true);
        session.setFlushMode(FlushMode.MANUAL);
        return session;
    }

    /**
     * Close the given Session. Note that this just applies in single session
     * mode!
     * <p>
     * Can be overridden in subclasses, e.g. for flushing the Session before
     * closing it. See class-level javadoc for a discussion of flush handling.
     * Note that you should also override getSession accordingly, to set the
     * flush mode to something else than NEVER.
     * 
     * @param session
     *            the Session used for filtering
     * @param sessionFactory
     *            the SessionFactory that this filter uses
     */
    protected void closeSession(Session session, SessionFactory sessionFactory)
    {
        SessionFactoryUtils.closeSession(session);
    }

}
